package com.my.springboot.tuling.jvm.concurrency;

import com.my.springboot.tuling.jvm.concurrency.factory.UnsafeFactory;
import org.springframework.objenesis.instantiator.sun.UnsafeFactoryInstantiator;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.LockSupport;

/**
 * 可见性问题分析
 *  -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp
 *
 *  为什么多线程对共享变量的操作存在可见性问题？
 *
 *  总结：  Java中可见性如何保证？ 方式归类有两种：
 *           1.  jvm层面 storeLoad内存屏障    ===>  x86   lock替代了mfence
 *              (1)插入一个内存屏障，相当于告诉CPU和编译器先于这个命令的必须先执行，后于这个命令的必须后执行
 *              (2)内存屏障另一个作用是强制更新一次不同CPU的缓存。例如，一个写屏障会把这个屏障前写入的数据刷新到缓存，
 *                  这样任何试图读取该数据的线程将得到最新值，而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。
 *           2.  上下文切换   Thread.yield();
 */
public class VisibilityTest {
    private boolean flag = true;
    int count = 0;

    public void refresh(){
        flag = false;
        System.out.println(Thread.currentThread().getName()+"修改flag=" + flag);
        Set s = new HashSet();
    }

    public void load(){
        System.out.println(Thread.currentThread().getName() + "开始执行。。。");
        while (flag) {//while（true）抢占时间片优先级较高，饥饿
            // todo 业务逻辑
            count++;
            // 没有跳出循环  可见性问题
            // JMM模型 ：线程间通信，共享内存模型。cpu寄存器先从本地内存加载变量，
            // 本地内存不存在则从主存加载复制一份副本到本地内存，主存修改，不会立马刷主存，但是会在线程结束前刷回主存
            //
            /**解决方式：1、volatile private boolean flag = true;
             *          2、UnsafeFactory.getUnsafe().storeFence(); 内存屏障
             *          3、Thread.yield();   释放时间片，上下文切（换寄存器的值会被淘汰），保存上下文，如果存在写操作，会刷回主存，加载上下文会从主存加载到最新的值：flag=true
             *          4、System.out.println(count); println方法 有synchronized锁保证可见性，实际底层为内存屏障
             *          5、LockSupport.unpark(Thread.currentThread());  发放许可
             *          6、volatile int count = 0;
             *          7、Integer count = 0;
             */
            //UnsafeFactory.getUnsafe().storeFence();
            //Thread.yield();
            //System.out.println(count);
            //LockSupport.unpark(Thread.currentThread());

            // 本地缓存具体淘汰时间，不确定，硬件层面不好确定
            //shortWait(1000000);// 模拟程序执行1ms, 本地内存被淘汰清空，重新从主存加载变量到本地内存，所以会跳出循环
            //shortWait(2);// 执行2纳秒, 本地内存不会被淘汰清空，不会重新从主存加载变量到本地内存，所以不会跳出循环，


        }
        System.out.println(Thread.currentThread().getName() + "跳出循环，count=" + count);
    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityTest test = new VisibilityTest();
        // 线程threadA模拟数据加载场景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();

        // 让threadA执行一会儿
        Thread.sleep(1000);
        // 线程threadB通过flag控制threadA的执行时间
        Thread threadB = new Thread(() ->test.refresh(), "threadB");
        threadB.start();
    }

    public static void shortWait(long interval) {
        long start = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        }while (start + interval >= end);
    }
}
